package com.rtsapp.server.simulator;



import com.rtsapp.server.network.protocol.command.ICommand;
import com.rtsapp.server.utils.StringUtils;

import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Scanner;

/***
 * Command的工具类 * 读取command
 */

public class CommandFormater {

    public interface InputFilter{

        String processInput( String input );

    }

    private final String projectCommandPackage;
    private final InputFilter inputProcessor;

    public CommandFormater(String projectCommandPackage, InputFilter inputProcessor ) {
        this.projectCommandPackage = projectCommandPackage;
        this.inputProcessor = inputProcessor;
    }

    private void getFieldValue(Object obj, Field field, Scanner input) throws IllegalArgumentException, IllegalAccessException {
        String type = field.getType().getSimpleName();
        while (true) {
            switch (type) {
                case "boolean":
                case "Boolean":
                    System.out.print(String.format("%s: ", field.getName()));
                    try {
                        field.setBoolean(obj, input.nextBoolean());
                        return;
                    } catch (InputMismatchException e) {
                        System.err.println("Please enter a valid boolean type data");
                        input.nextLine();
                    }
                    break;

                case "byte":
                case "Byte":
                    System.out.print(String.format("%s: ", field.getName()));
                    try {
                        field.setByte(obj, input.nextByte());
                        return;
                    } catch (InputMismatchException e) {
                        System.err.println("Please enter a valid byte type data");
                        input.nextLine();
                    }
                    break;

                case "short":
                case "Short":
                    System.out.print(String.format("%s: ", field.getName()));
                    try {
                        field.setShort(obj, input.nextShort());
                        return;
                    } catch (InputMismatchException e) {
                        System.err.println("Please enter a valid short type data");
                        input.nextLine();
                    }
                    break;

                case "int":
                case "Integer":
                    System.out.print(String.format("%s: ", field.getName()));
                    try {
                        field.setInt(obj, input.nextInt());
                        return;
                    } catch (InputMismatchException e) {
                        System.err.println("Please enter a valid int type data");
                        input.nextLine();
                    }
                    break;

                case "long":
                case "Long":
                    System.out.print(String.format("%s: ", field.getName()));
                    try {
                        field.setLong(obj, input.nextLong());
                        return;
                    } catch (InputMismatchException e) {
                        System.err.println("Please enter a valid long type data");
                        input.nextLine();
                    }
                    break;

                case "float":
                case "Float":
                    System.out.print(String.format("%s: ", field.getName()));
                    try {
                        field.setFloat(obj, input.nextFloat());
                        return;
                    } catch (InputMismatchException e) {
                        System.err.println("Please enter a valid float type data");
                        input.nextLine();
                    }
                    break;
                case "double":
                case "Double":
                    System.out.print(String.format("%s: ", field.getName()));
                    try {
                        field.setDouble(obj, input.nextDouble());
                        return;
                    } catch (InputMismatchException e) {
                        System.err.println("Please enter a valid double type data");
                        input.nextLine();
                    }
                    break;
                case "String":
                    System.out.print(String.format("%s: ", field.getName()));
                    String valueStr = input.nextLine();
                    if (StringUtils.isEmpty(valueStr)) {
                        valueStr = input.nextLine();
                    }
                    field.set(obj, valueStr);
                    return;
                case "List":
                case "ArrayList": {
                    int size = 0;
                    System.out.print(String.format("Please enter the number of elements in the %s: ", field.getName()));
                    while (true) {
                        try {
                            size = input.nextInt();
                            break;
                        } catch (InputMismatchException e) {
                            System.err.println("Please enter a valid int type data");
                        }
                    }
                    ParameterizedType pType = (ParameterizedType) field.getGenericType();
                    Class clz = (Class) pType.getActualTypeArguments()[0];

                    for (int i = 0; i < size; i++) {
                        System.out.println(String.format("Please enter the first %d %s: ", i + 1, clz.getSimpleName()));
                        try {

                            Method method = field.get(obj).getClass().getDeclaredMethod("add", Object.class);

                            boolean isBaseDataType = true;
                            while (true) {
                                boolean success = false;

                                switch (clz.getSimpleName()) {
                                    case "Boolean":
                                        System.out.print(String.format("%s: ", field.getName()));
                                        try {
                                            method.invoke(field.get(obj), input.nextBoolean());
                                            success = true;
                                            break;
                                        } catch (InputMismatchException e) {
                                            System.err.println("Please enter a valid boolean type data");
                                        } catch (InvocationTargetException e) {
                                            e.printStackTrace();
                                        }
                                        break;
                                    case "Integer":
                                        System.out.print(String.format("%s: ", field.getName()));
                                        try {
                                            method.invoke(field.get(obj), input.nextInt());
                                            success = true;
                                            break;
                                        } catch (InputMismatchException e) {
                                            System.err.println("Please enter a valid int type data");
                                        } catch (InvocationTargetException e) {
                                            e.printStackTrace();
                                        }
                                        break;
                                    case "Float":
                                        System.out.print(String.format("%s: ", field.getName()));
                                        try {
                                            method.invoke(field.get(obj), input.nextFloat());
                                            success = true;
                                            break;
                                        } catch (InputMismatchException e) {
                                            System.err.println("Please enter a valid float type data");
                                        } catch (InvocationTargetException e) {
                                            e.printStackTrace();
                                        }
                                        break;
                                    case "String":
                                        System.out.print(String.format("%s: ", field.getName()));
                                        try {
                                            method.invoke(field.get(obj), input.nextLine());
                                            success = true;
                                        } catch (InvocationTargetException e) {
                                            e.printStackTrace();
                                        }
                                        break;
                                    default:
                                        isBaseDataType = false;
                                        success = true;
                                        break;
                                }
                                if (success) {
                                    break;
                                }
                            }
                            if (!isBaseDataType) {
                                Object item = clz.newInstance();

                                method.invoke(field.get(obj), item );

                                for (Field f : clz.getDeclaredFields()) {
                                    getFieldValue(item, f, input);
                                }
                            }
                        } catch (InstantiationException e) {
                            e.printStackTrace();
                        } catch (NoSuchMethodException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
                    return;
                }
                default:
                    System.out.println(String.format("Please enter %s property", field.getName()));
                    Object tmpObj = field.get(obj);
                    for (Field subField : tmpObj.getClass().getFields()) {
                        getFieldValue(tmpObj, subField, input);
                    }
                    return;
            }
        }
    }

    /**
     * 将一个命令对象以格式化的方式输出到输出流
     *
     * @param out
     * @param object
     */
//    public static void writeCommandToCmd(PrintStream out, Object object, List<Boolean> isLast) {
//        // 将命令格式输出
//        synchronized (out) {
//            if (object == null) {
//                out.print("= null");
//                return;
//            }
//            Class cls = (Class) object.getClass();
//            try {
//                Field[] fields = cls.getFields();
//                for (int k = 0; k < fields.length; k++) {
//                    if (k == fields.length - 1) {
//                        isLast.add(true);
//                    } else {
//                        isLast.add(false);
//                    }
//                    writeSymbol(out, isLast);
//                    out.print(String.format(" %s ", fields[k].getName()));
//                    switch (fields[k].getType().getSimpleName()) {
//                        case "int":
//                        case "Integer":
//                            if (fields[k].getName().equals("errorNo")) {
//                                out.print("= ");
//                                out.print(String.format("0x%s", Integer.toHexString((Integer) fields[k].get(object))));
//                                break;
//                            }
//                        case "boolean":
//                        case "Boolean":
//                        case "float":
//                        case "Float":
//                        case "long":
//                        case "Long":
//                        case "String":
//                            out.print("= ");
//                            out.print(fields[k].get(object));
//                            break;
//                        case "List":
//                        case "ArrayList":
//                            List list = (List) fields[k].get(object);
//                            for (int i = 0; i < list.size(); i++) {
//                                if (i == list.size() - 1) {
//                                    isLast.add(true);
//                                } else {
//                                    isLast.add(false);
//                                }
//                                writeSymbol(out, isLast);
//                                out.print(String.format(" %s ", ((Class) ((ParameterizedType) fields[k].getGenericType()).getActualTypeArguments()[0]).getSimpleName()));
//
//                                writeCommandToCmd(out, list.get(i), isLast);
//                                isLast.remove(isLast.size() - 1);
//                            }
//                            break;
//                        default:
//                            writeCommandToCmd(out, fields[k].get(object), isLast);
//                            break;
//                    }
//                    isLast.remove(isLast.size() - 1);
//                }
//            } catch (IllegalAccessException e) {
//                e.printStackTrace();
//            }
//        }
//    }
    public  void writeCommandToCmd(PrintStream out, Object object, String varName, List<Boolean> isLast) {
        // 将命令格式输出
        synchronized (out) {

            Class cls = null;
            if( object != null ){
                cls = (Class) object.getClass();
            }

            if( varName != null ){
                out.print( String.format(" %s ", varName ) );
            }else{
                if( cls != null ) {
                    out.print(String.format(" %s ", cls.getSimpleName()));
                }
            }

            if (object == null) {
                out.print("= null");
                return;
            }


            try {

                switch ( cls.getSimpleName()) {
                    case "int":
                    case "Integer":
                        if( varName != null && varName.equals("errorNo") ){
                            out.print("= ");
                            out.print( String.format("0x%s", Integer.toHexString( (Integer)object ) ) );
                            break;
                        }
                    case "byte":
                    case "Byte":
                    case "short":
                    case "Short":
                    case "boolean":
                    case "Boolean":
                    case "float":
                    case "Float":
                    case "long":
                    case "Long":
                    case "String":
                        out.print("= ");
                        out.print( object );
                        break;
                    case "List":
                    case "ArrayList":
                        List list = (List)object;
                        for (int i = 0; i < list.size(); i++) {
                            if (i == list.size() - 1) {
                                isLast.add(true);
                            } else {
                                isLast.add(false);
                            }
                            writeSymbol(out, isLast);

                            writeCommandToCmd(out, list.get(i), null, isLast);

                            isLast.remove(isLast.size() - 1);
                        }
                        break;
                    default: {
                        Field[] fields = cls.getFields();
                        for (int k = 0; k < fields.length; k++) {

                            if (k == fields.length - 1) {
                                isLast.add(true);
                            } else {
                                isLast.add(false);
                            }

                            writeSymbol(out, isLast);

                            writeCommandToCmd(out, fields[k].get(object), fields[k].getName(), isLast);

                            isLast.remove(isLast.size() - 1);
                        }
                        break;
                    }
                }

            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 输出缩进符号
     */
    private void writeSymbol(PrintStream out, List<Boolean> isLast) {
        System.out.println();
        for (int j = 0; j < isLast.size() - 1; j++) {
            System.out.print(" ");
            if (j < isLast.size() - 2) {
                if (isLast.get(j + 1))
                    System.out.print("  ");
                else
                    System.out.print(" │");
            } else {
                if (isLast.get(isLast.size() - 1))
                    System.out.print(" └");
                else
                    System.out.print(" ├");
            }
        }
    }

    /**
     * 引导用户输入 命令名，每一个字段的值，并得到对应的command对象
     *
     * @param input
     * @return
     */
    public ICommand getCommandFromCmd(Scanner input) {
        // 请输入命令名称, GameInitDataRequest
        while (true) {
            String commandName = null;
            try {
                 commandName = input.next();
            }catch ( Throwable ex ){
                System.out.println( "simulator exit" );
                System.exit( 0 );
            }

            if( inputProcessor != null ){
                commandName = inputProcessor.processInput( commandName );
            }

            if( null == commandName ){
                return null;
            }

            String likeCommandName = null;
            String startCommandName = null;
            String startCommandName2 = null;
            String endCommandName = null;
            String endCommandName2 = null;

            Field likeField = null;
            Field startField = null;
            Field startField2 = null;
            Field endField = null;
            Field endField2 = null;
            try {
                Class<?> commandsCls = Class.forName(this.projectCommandPackage + ".Commands");
                for (Field field : commandsCls.getFields()) {
                    if (field.getName().equals(commandName)) {
                        try {
                            Class<ICommand> commandClass = (Class<ICommand>) Class.forName(String.format(this.projectCommandPackage + ".%sRequest", field.getName()));
                            ICommand command = commandClass.newInstance();
                            for (Field commandField : commandClass.getFields()) {
                                getFieldValue(command, commandField, input);
                            }
                            return command;
                        } catch (Throwable e) {
                            e.printStackTrace();
                        }
                        //仅大小写不同
                    } else if (likeCommandName == null && field.getName().toLowerCase().equals(commandName.toLowerCase())) {
                        likeCommandName = field.getName();
                        likeField = field;
                        //以输入内容开始
                    } else if (startCommandName == null && field.getName().toLowerCase().startsWith(commandName.toLowerCase())) {
                        startCommandName = field.getName();
                        startField = field;
                        //以输入内容结尾
                    } else if (endCommandName == null && field.getName().toLowerCase().endsWith(commandName.toLowerCase())) {
                        endCommandName = field.getName();
                        endField = field;
                        //与输入内容前半部分相同
                    } else if ((field.getName().length() / 2) > 0
                            && (commandName.length() / 2) > 0
                            && startCommandName2 == null && field.getName().length() >= commandName.length() / 2 && field.getName().toLowerCase().substring(0, commandName.length() / 2).startsWith(commandName.toLowerCase().substring(0, commandName.length() / 2))) {
                        startCommandName2 = field.getName();
                        startField2 = field;
                        //与输入内容首字母相同，与输入内容后半部分相同
                    } else if ((field.getName().length() / 2) > 0
                            && (commandName.length() / 2) > 0
                            && endCommandName2 == null && field.getName().length() >= commandName.length() && field.getName().toLowerCase().charAt(0) == commandName.toLowerCase().charAt(0) && field.getName().toLowerCase().substring(commandName.length() / 2).startsWith(commandName.toLowerCase().substring(commandName.length() / 2))) {
                        endCommandName2 = field.getName();
                        endField2 = field;
                    }
                }
                if (likeCommandName != null) {
                } else if (startCommandName != null) {
                    likeCommandName = startCommandName;
                    likeField = startField;
                } else if (endCommandName != null) {
                    likeCommandName = endCommandName;
                    likeField = endField;
                } else if (endCommandName != null) {
                    likeCommandName = startCommandName2;
                    likeField = startField2;
                } else if (endCommandName != null) {
                    likeCommandName = endCommandName2;
                    likeField = endField2;
                } else {
                    System.err.println("\ncommand not found!");
                    continue;
                }
                System.err.println("\ncorrect '" + commandName + "' to '" + likeCommandName + "' [y/n]?");
                String inputStr = input.nextLine();
                if (inputStr.length() == 0) {
                    inputStr = input.nextLine();
                }
                if (inputStr.length() > 0 && (inputStr.charAt(0) == 'y' || inputStr.charAt(0) == 'Y')) {
                    try {
                        Class<ICommand> commandClass = (Class<ICommand>) Class.forName(String.format(this.projectCommandPackage + ".%sRequest", likeField.getName()));
                        ICommand command = commandClass.newInstance();
                        for (Field commandField : commandClass.getFields()) {
                            getFieldValue(command, commandField, input);
                        }
                        return command;
                    } catch (Throwable e) {
                        e.printStackTrace();
                    }
                } else if (inputStr.length() > 0 && (inputStr.charAt(0) == 'n' || inputStr.charAt(0) == 'N')) {
                    System.err.println("\ncommand not found!");
                } else {
                    System.err.println("\ncommand not found!");
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
}
